import { PayloadAction, createAsyncThunk, createSlice, isRejected } from "@reduxjs/toolkit";
import { RootState } from "./store";
import { encryptSha256 } from "../util/Crypto";
import { request } from "../util/Net";
import { globalActions } from "./Global";

type AnalyzeResult = 'good' | 'bad';

// 1. Data structure and combinations.

interface ModelEntry {
  id: number;
  name: string;
  accuracy: number;
};


// 2. Requests and Responses, for communication with backend.

// 2.1 Requests.

/**
 * Request to analyze an image.
 * @path /service/analyze/image
 */
interface AnalyzeImageRequest {
  /**
   * ID of the image, specifically a 32-character hash of data.
   */
  id: string;
  /**
   * A Base64-encoded string describing the image, starting with 'data:url'.
   */
  image: string;
  /**
   * Model ID to use in this analyze.
   */
  model: number;
};

type FetchAllModelsRequest = null;

// 2.2 Responses.

/**
 * Response from analyze image module.
 * @errcode 10002
 * @path /service/analyze/image
 */
interface AnalyzeImageResponse {
  /**
   * Result of the analysis, should be 'good' or 'bad'.
   */
  result: AnalyzeResult;
};

type FetchAllModelsResponse = ModelEntry[];

// 3. Params for reducers.

interface UpdateImageParams {
  image: string;
};

interface AnalyzeImageParams {
  image: string;
  model: number;
};

interface SelectModelParams {
  id: number;
};

// 4. State and initial value definition.

/**
 * Redux state repository for this slice.
 */
interface AnalyzeState {
  loaded: boolean;
  image: string;
  resultLoaded: boolean;
  result: AnalyzeResult;
  modelLoaded: boolean;
  models: ModelEntry[];
  selectedModelId: number;
};

/**
 * Initial state of this slice.
 */
const initialState: AnalyzeState = {
  loaded: false,
  image: '',
  resultLoaded: false,
  result: 'bad',
  modelLoaded: false,
  models: [],
  selectedModelId: 0
};

// 5. Async functions definition.

/**
 * Async reducer.
 * Analyze the given image, and judge if the image is 'good' or 'bad'.
 * @param params params of the reducer, including the Base64-encoded image
 * @example const dispatch = useAppDispatch();
 * dispatch(analyzeImage({ image: 'data:url:...' }));
 */
const analyzeImage = createAsyncThunk('api/analyzeImage', async (params: AnalyzeImageParams, thunkAPI) => {
  const image: string = params.image;
  const id: string = encryptSha256(image);
  const response = await request<AnalyzeImageResponse, AnalyzeImageRequest>('/service/image/analyze', {
    id: id,
    image: image,
    model: params.model
  });
  if (response.data.code !== 0) {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Error', message: response.data.message, type: 'warning' }));
    return thunkAPI.rejectWithValue(response.data);
  }
  return response.data;
});

const fetchAllModels = createAsyncThunk('api/fetchAllModels', async (_: void, thunkAPI) => {
  const response = await request<FetchAllModelsResponse, FetchAllModelsRequest>('/service/model/fetchAll');
  return response.data;
});

// 6. Slice definition.

/**
 * Slice of the module Analyze.
 */
const analyzeSlice = createSlice({
  name: 'analyze',
  initialState: initialState,
  reducers: {
    /**
     * Clear all states in Analyze module.
     */
    clear: (state) => {
      state.loaded = false;
      state.image = '';
      state.resultLoaded = false;
      state.result = 'bad';
    },
    /**
     * Clear result states in Analyze module.
     */
    clearResult: (state) => {
      state.resultLoaded = false;
      state.result = 'bad';
    },
    /**
     * Update image data, and clear previous analyze data.
     * @param action action with payload, instructing the path of image to analyze
     */
    updateImage: (state, action: PayloadAction<UpdateImageParams>) => {
      const image: string = action.payload.image;
      state.loaded = true;
      state.image = image; 
      state.resultLoaded = false;
      state.result = 'bad';
    },
    updateSelectedModelId: (state, action: PayloadAction<SelectModelParams>) => {
      state.selectedModelId = action.payload.id;
    }
  },
  extraReducers: (builder) => {
    builder.addCase(analyzeImage.fulfilled, (state, action) => {
      const result: AnalyzeResult = action.payload.result.result;
      state.resultLoaded = true;
      state.result = result;
    }).addCase(fetchAllModels.fulfilled, (state, action) => {
      state.modelLoaded = true;
      state.models = action.payload.result;
      state.selectedModelId = action.payload.result[0].id;
    }).addMatcher(isRejected, (state, action) => {
      console.error(action);
    });
  }
});

// 7. Selectors for state in this module.

export const selectAnalyzeLoaded = (state: RootState) => state.Analyze.loaded;
export const selectAnalyzeImagePath = (state: RootState) => state.Analyze.image;
export const selectAnalyzeResultLoaded = (state: RootState) => state.Analyze.resultLoaded;
export const selectAnalyzeResult = (state: RootState) => state.Analyze.result;
export const selectAnalyzeSelectedModelId = (state: RootState) => state.Analyze.selectedModelId;
export const selectAnalyzeModelLoaded = (state: RootState) => state.Analyze.modelLoaded;
export const selectAnalyzeModels = (state: RootState) => state.Analyze.models;

// 8. Export necessary modules.
export const analyzeActions = analyzeSlice.actions;
export { analyzeImage, fetchAllModels };
export type { AnalyzeResult, ModelEntry };
export default analyzeSlice.reducer;
